﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using MyMediaLite.Data;
using System.IO;
using MyMediaLite.RatingPrediction;
using MyMediaLite;

namespace recommenderSystem
{
    class MyCascadeHybrid : RatingPredictor
    {
        SlopeOne slopy;
        UserKNNCosine knn;
        //if true slope first, if false knn first
        public bool RecommenderSequence;

        public override MyMediaLite.Data.IRatings Ratings
        {
            get
            {
                return base.Ratings;
            }
            set
            {
                base.Ratings = value;
                slopy.Ratings = value;
                knn.Ratings = value;
            }
        }

        //bool flag for the recommender sequenz of this hybrid
        //true -> slope first
        //false -> knn first
        public uint K
        {
            get
            {
                return knn.K;
            }
            set
            {
                knn.K = value;
            }
        }

        public MyCascadeHybrid(bool flag)
        {
            slopy = new SlopeOne();
            knn = new UserKNNCosine();
            //set the flag
            RecommenderSequence = flag;
        }

        public override void Train()
        {
            slopy.Train();
            knn.Train();
        }

        // first step calculation to cut off Itemlist
        private Ratings calcFirstEvaluation(double threshold, IRatings testratings)
        {

            var user_mapping = new IdentityMapping();
            var item_mapping = new IdentityMapping();

            var resRatings = new Ratings();
            int userId, itemId;
            double predictedValues;

            IRecommender predictMethod;
            if (RecommenderSequence)
            {
                Console.WriteLine("using: slopy");
                predictMethod = slopy;
            }
            else
            {
                Console.WriteLine("using: knn");
                predictMethod = knn;
            }
            for (int index = 0; index < testratings.Count; index++)
            {
                userId = testratings.Users[index];
                itemId = testratings.Items[index];
                //predictedValues = this.PredictWithFlag(userId, itemId,recommenderSequenz);

                predictedValues = predictMethod.Predict(userId, itemId);

                // only add values if predicted value is above threshold
                if (predictedValues >= threshold)
                {
                    resRatings.Add(userId, itemId, predictedValues);
                }
            }

            Console.WriteLine("{2} predicted (count = {0} / total = {1})", resRatings.Count, testratings.Count, RecommenderSequence ? "slopy" : "knn");

            return resRatings;
        }

        
        //secondStep Calculation
        private Ratings calcSecondEvaluation(double threshold, IRatings resOfFirst) {
           
            var user_mapping = new IdentityMapping();
            var item_mapping = new IdentityMapping();

            var resRatings = new Ratings();
            int userId, itemId;
            double predictedValues;
            bool useSlopyMethod = !RecommenderSequence;
            
            IRecommender predictMethod;
            if (useSlopyMethod)
            {
                Console.WriteLine("using: slopy");
                predictMethod = slopy;
            }
            else
            {
                Console.WriteLine("using: knn");
                predictMethod = knn;
            }

            for (int index = 0; index < resOfFirst.Count; index++)
            {
                userId = resOfFirst.Users[index];
                itemId = resOfFirst.Items[index];
                
                predictedValues = predictMethod.Predict(userId, itemId);
                
                predictedValues = (resOfFirst[index] + predictedValues) * 0.5;
                // only add values if predicted value is above threshold
                if (predictedValues >= threshold)
                {
                    resRatings.Add(userId, itemId, predictedValues);
                }
            }

            Console.WriteLine("{2} predicted (count = {0} / total = {1})", resRatings.Count, resOfFirst.Count, useSlopyMethod ? "slopy" : "knn");

            //final resRating
            return resRatings;
        }

        public Ratings doItemPrediction(double threshold, IRatings testRatings) {
            Tools.Tic(string.Format("doItemPrediction (threshold = {0}, count = {1})", threshold, testRatings.Count));
            
            Ratings firstResult = calcFirstEvaluation(threshold, testRatings);
            Ratings secondRating = calcSecondEvaluation(threshold, firstResult);

            Tools.Toc();

            return secondRating;
        }

        public override double Predict(int user_id, int item_id)
        {
            throw new NotImplementedException();
        }
    }
}
